﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Fluq
{
	public sealed class FactoryDependencyResolver : IDependencyResolver
	{
		private IDependencyResolver _resolver;

		public FactoryDependencyResolver(IDependencyResolver resolver)
		{
			if (resolver == null)
			{
				throw new ArgumentNullException("resolver");
			}

			_resolver = resolver;
		}

		public bool TryResolve(object graphId, IDependencyContainer container, IDependencyKey key, out object instance)
		{
			if (!_resolver.TryResolve(graphId, container, key, out instance))
			{
				Type type = key.Type;

				if (type.IsGenericType)
				{
					Type genericType = type.GetGenericTypeDefinition();

					if (genericType == typeof(Func<>))
					{
						Type dependencyType = type.GetGenericArguments()[0];

						IFactoryResolver resolver = (IFactoryResolver)Activator.CreateInstance(typeof(FactoryResolver<>).MakeGenericType(dependencyType));

						instance = resolver.Resolve(graphId, container, new FactoryKey(dependencyType, key.Id));

						if (instance != null)
						{
							return true;
						}
					}
				}

				return false;
			}
			else
			{
				return true;
			}
		}

		public bool TryResolve(IDependencyContainer container, IDependencyKey key, out object instance)
		{
			return _resolver.TryResolve(container, key, out instance);
		}

		public bool TryRelease(IDependencyContainer container, object instance)
		{
			return _resolver.TryRelease(container, instance);
		}

		interface IFactoryResolver
		{
			object Resolve(object graphId, IDependencyContainer container, IDependencyKey key);
		}

		sealed class FactoryResolver<T> : IFactoryResolver
		{
			private Func<T> ResolveInternal(object graphId, IDependencyContainer container, IDependencyKey key)
			{
				return () =>
				{
					object instance;
					
					container.Resolver.TryResolve(graphId, container, key, out instance);

					return (T)instance;
				};
			}

			public object Resolve(object graphId, IDependencyContainer container, IDependencyKey key)
			{
				return ResolveInternal(graphId, container, key);
			}
		}

		sealed class FactoryKey : IDependencyKey
		{
			private Type _type;
			private IComparable _id;

			public FactoryKey(Type type, IComparable id)
			{
				_type = type;
				_id = id;
			}

			public Type Type { get { return _type; } }

			public IComparable Id { get { return _id; } }
		}
	}
}
